﻿using System.Collections.Generic;
using System.Linq;
using System.Xml;
using DataScripter.Containers;
using DataScripter.Database;
using DataScripter.Scripting;
using DataScripter.Scripting.Fluent;
using DataScripter.Scripting.ScriptDefinitions;

namespace DataScripter.XmlInterpreter.XmlInterpreters
{
    public class ScriptDefinitionInterpreter : IXmlInterpreter<IScriptDefinition>
    {
        private IContainer Container { get; set; }
        private IScriptDefinition ScriptDefinition { get; set; }

        public ScriptDefinitionInterpreter(IContainer container)
        {
            Container = container;
        }

        public IScriptDefinition InterpretElement(XmlElement element)
        {
            string name = element.GetAttribute("Name");
            ScriptDefinition = new ScriptDefinition(name, Container);

            foreach (XmlElement child in element.ChildNodes.OfType<XmlElement>())
            {
                switch (child.Name)
                {
                    case "Table":
                        InterpretTable(child, null);
                        break;
                }
            }
            return ScriptDefinition;
        }

        private void InterpretTable(XmlElement element, IFluentTableScriptRequest parent)
        {
            string tableName = element.GetAttribute("Name");
            string whereClause = element.GetAttribute("Where");
            IFluentTableScriptRequest tableScriptRequest = ScriptDefinition.RequestScript(tableName);
            if (!string.IsNullOrEmpty(whereClause))
            {
                tableScriptRequest.Where(whereClause);
            }
            if (parent != null)
            {
                string joinOn = element.GetAttribute("JoinOn");
                if (string.IsNullOrEmpty(joinOn))
                {
                    // find foreign key to relate the parent with the child
                    IForeignKey key = FindUniqueForeignKey(tableScriptRequest.Request, parent.Request);
                    if (key != null)
                    {
                        tableScriptRequest.FilterByForeignKey(key);
                    }
                }
                else
                {
                    tableScriptRequest.Where(string.Format("{0} IN (SELECT {0} FROM {1} WHERE {1}.{0}={2}.{0} AND {3})", joinOn, parent.Request.TableName, tableScriptRequest.Request.TableName, parent.Request.ProduceFilter()));
                }
            }
            foreach (XmlElement child in element.ChildNodes.OfType<XmlElement>())
            {
                switch (child.Name)
                {
                    case "Table":
                        InterpretTable(child, tableScriptRequest);
                        break;
                    case "Or":
                        IFluentOrFilterRequest or = tableScriptRequest.BeginOr();
                        tableScriptRequest = InterpretOrElement(child, or, parent);
                        break;
                    case "Where":
                        whereClause = child.GetAttribute("Clause");
                        tableScriptRequest.Where(whereClause);
                        break;
                    case "FilterByForeignKey":
                        string foreignKeyName = child.GetAttribute("Name");
                        string foreignTable = child.GetAttribute("OnTable");
                        ITableStructure tableStructure = Container.Resolve<IDatabaseStructure>().GetTable(string.IsNullOrEmpty(foreignTable) ? tableName : foreignTable);
                        IForeignKey foreignKey = tableStructure.ForeignKeys.Single(fk => fk.Name.ToUpper() == foreignKeyName.ToUpper());
                        if (foreignKey != null)
                        {
                            tableScriptRequest.FilterByForeignKey(foreignKey);
                        }
                        break;
                    case "Lookup":
                        string fieldName = child.GetAttribute("Name");
                        string lookupTable = child.GetAttribute("LookupTable");
                        string lookupField = child.GetAttribute("LookupField");
                        tableScriptRequest.Lookup(fieldName, lookupTable, lookupField);
                        break;
                }
            }
            return;
        }

        private static IFluentTableScriptRequest InterpretOrElement(XmlElement element, IFluentOrFilterRequest or, IFluentTableScriptRequest parent)
        {
            foreach (XmlElement childElement in element.ChildNodes.OfType<XmlElement>())
            {
                switch (childElement.Name)
                {
                    case "FilterByForeignKey":
                        string foreignKeyName = childElement.GetAttribute("Name");

                        // this one might not exist
                        IForeignKey foreignKey = or.Request.TableStructure.ForeignKeys.SingleOrDefault(fk => fk.Name.ToUpper() == foreignKeyName.ToUpper())
                            // then this one should
                                                 ?? parent.Request.TableStructure.ForeignKeys.Single(fk => fk.Name.ToUpper() == foreignKeyName.ToUpper());

                        if (foreignKey.TableName != or.Request.TableName)
                            foreignKey = foreignKey.CreateReverseInMemoryForeignKey();
                        or.FilterByForeignKey(foreignKey);
                        break;
                    case "Where":
                        string whereClause = childElement.GetAttribute("Clause");
                        or.Where(whereClause);
                        break;
                }
            }
            return or.EndOr();
        }

        private static IForeignKey FindUniqueForeignKey(ITableScriptRequest child, ITableScriptRequest parent)
        {
            IEnumerable<IForeignKey> fksChild2Parent = child.TableStructure.ForeignKeys.Where(fk => fk.TableName == child.TableName && fk.ReferencedTableName == parent.TableName);
            IEnumerable<IForeignKey> fksParent2Child = parent.TableStructure.ForeignKeys.Where(fk => fk.TableName == parent.TableName && fk.ReferencedTableName == child.TableName);
            IEnumerable<IForeignKey> allForeignKeys = fksChild2Parent.Union(fksParent2Child);
            return allForeignKeys.Count() == 1 ? allForeignKeys.First() : null;
        }


    }
}
